/*   Carina
 *   Copyright (C) 2009 2010 2011  Zdravko Velinov
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _CARINA_EDITOR_PROPERTY_HH_
#define _CARINA_EDITOR_PROPERTY_HH_

#include "carina-qt-meta-types.hh"

#include <QObject>
#include <QVariant>

#include "carina/common/global.hh"
#include "carina/common/memory.hh"
#include "carina/common/containers.hh"
#include "carina/common/ces-file.hh"
#include "carina/attribute.hh"
#include "carina/utils.hh"

using namespace Carina;

enum
{
    PROPERTY_NAME,
    PROPERTY_VALUE,
    PROPERTY_COLUMN_COUNT
};

class Property;
typedef shared_ptr<Property> PropertyPtr;

class Property: public QObject
{
    Q_OBJECT

    friend class PropertyTreeModel;

    Property*                   m_Parent;
    DynamicArray<PropertyPtr>   m_Children;
public:
    Property();
    virtual ~Property();

    Property* getParent();
    const Property* getParent() const;
    void setParent(Property* parent);
    void addChild(const PropertyPtr& prop);
    Property& getChild(size_t idx);
    const Property& getChild(size_t idx) const;
    void removeChildren(size_t lhs, size_t rhs);
    size_t getChildrenCount() const;
    size_t getIndex(Property* ptr) const;
    void clearChildren();

    virtual UniformVarType getType() const=0;
    virtual VarRange getRange() const { return CE_RANGE_NONE; }

private:
    virtual Qt::ItemFlags flags(int col) const=0;
    virtual QVariant data(int col, int role) const=0;
    virtual bool setData(const QVariant& value, int role = Qt::EditRole)=0;

    virtual void _changed()=0;

signals:
    void changed();

private slots:
    void update();
};

template<class T>
class ValueProperty: public Property
{
    QString m_Name;
    T       m_Value;
public:
    ValueProperty(const QString& name)
        :   m_Name(name) {}

    virtual UniformVarType getType() const { return UVTInfo<T>::value(); }

    T getValue() const { return m_Value; }
    void setValue(const T& t) { m_Value = t; }

private:
    virtual Qt::ItemFlags flags(int col) const
    {
        switch(col)
        {
        case PROPERTY_NAME: return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
        case PROPERTY_VALUE: return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
        }
        return Qt::NoItemFlags;
    }

    virtual QVariant data(int col, int role) const
    {
        if(role == Qt::DisplayRole)
            switch(col)
            {
            case PROPERTY_NAME: return QVariant(m_Name);
            case PROPERTY_VALUE: return QVariant(convert_value(m_Value).c_str());
            }
        return QVariant();
    }

    virtual bool setData(const QVariant& value, int role = Qt::EditRole)
    {
        if(!value.canConvert<T>())
            return false;
        m_Value = value.value<T>();
        emit changed();
        _changed();
        return true;
    }
protected:
    virtual void _changed() {}
};

template<>
class ValueProperty<Matrix4>: public Property
{
    enum
    {
        MATRIX_PROPERTY_TRANSLATION,
        MATRIX_PROPERTY_ROTATION,
        MATRIX_PROPERTY_SCALING
    };

    QString m_Name;
    Matrix4 m_Value;
public:
    ValueProperty(const QString& name)
        :   m_Name(name)
    {
        addChild(make_aligned_shared<ValueProperty<Vector3>>("Translation"));
        addChild(make_aligned_shared<ValueProperty<Vector3>>("Rotation"));
        addChild(make_aligned_shared<ValueProperty<Vector3>>("Scaling"));

        connect(&getChild(MATRIX_PROPERTY_TRANSLATION), SIGNAL(changed()), this, SLOT(update()));
        connect(&getChild(MATRIX_PROPERTY_ROTATION), SIGNAL(changed()), this, SLOT(update()));
        connect(&getChild(MATRIX_PROPERTY_SCALING), SIGNAL(changed()), this, SLOT(update()));
    }

    virtual UniformVarType getType() const { return UVTInfo<Matrix4>::value(); }

    Matrix4 getValue() const { return m_Value; }
    void setValue(const Matrix4& mat)
    {
        Vector3 t, s, r;
        m_Value = mat;
        m_Value.decompose(t, s, r);
        static_cast<ValueProperty<Vector3>&>(getChild(MATRIX_PROPERTY_TRANSLATION)).setValue(t),
        static_cast<ValueProperty<Vector3>&>(getChild(MATRIX_PROPERTY_ROTATION)).setValue(to_degrees(r)),
        static_cast<ValueProperty<Vector3>&>(getChild(MATRIX_PROPERTY_SCALING)).setValue(s);
    }

private:
    virtual Qt::ItemFlags flags(int col) const
    {
        int f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
        return static_cast<Qt::ItemFlags>(f);
    }

    virtual QVariant data(int col, int role) const
    {
        return role == Qt::DisplayRole && col == PROPERTY_NAME ? QVariant(m_Name) : QVariant();
    }

    virtual bool setData(const QVariant& value, int role = Qt::EditRole)
    {
        if(!value.canConvert<Matrix4>())
            return false;
        setValue(value.value<Matrix4>());
        emit changed();
        return true;
    }

protected:
    virtual void _changed()
    {
        Vector3 translation = static_cast<ValueProperty<Vector3>&>(getChild(MATRIX_PROPERTY_TRANSLATION)).getValue(),
                rotation = to_radians(static_cast<ValueProperty<Vector3>&>(getChild(MATRIX_PROPERTY_ROTATION)).getValue()),
                scaling = static_cast<ValueProperty<Vector3>&>(getChild(MATRIX_PROPERTY_SCALING)).getValue();
        m_Value.identity();
        m_Value.translate(translation);
        m_Value.rotate(rotation);
        m_Value.scale(scaling);
    }
};

template<template<class U, class A> class TArray, class T, class TAlloc>
class ValueProperty< TArray<T, TAlloc> >: public Property
{
    QString         m_Name;
    bool            m_IsEditable;
public:
    typedef ValueProperty<T> child_type;

    ValueProperty(const QString& name)
        :   m_Name(name),
            m_IsEditable(true)
    {
    }

    ValueProperty(const QString& name, size_t count, bool is_editable)
        :   m_Name(name),
            m_IsEditable(is_editable)
    {
        for(size_t i = 0; i < count; ++i)
        {
            addChild(make_aligned_shared<child_type>(convert_value(i).c_str()));
            connect(&getChild(i), SIGNAL(changed()), this, SLOT(update()));
        }
    }

    TArray<T, TAlloc> getValue() const
    {
        TArray<T, TAlloc> cont;
        for(size_t i = 0; i < getChildrenCount(); ++i)
            cont.push_back(static_cast<const child_type&>(getChild(i)).getValue());
        return cont;
    }

    void setValue(const TArray<T, TAlloc>& cont)
    {
        clearChildren();
        for(size_t i = 0; i < cont.size(); ++i)
        {
            addChild(make_aligned_shared<child_type>(convert_value(i).c_str()));
            static_cast<child_type&>(getChild(i)).setValue(cont[i]);
            connect(&getChild(i), SIGNAL(changed()), this, SLOT(update()));
        }
    }

    virtual UniformVarType getType() const { return UVTInfo<unsigned>::value(); }

private:
    virtual Qt::ItemFlags flags(int col) const
    {
        switch(col)
        {
        case PROPERTY_NAME: return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
        case PROPERTY_VALUE:
        {
            int f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
            if(m_IsEditable)
                f |= Qt::ItemIsEditable;
            return static_cast<Qt::ItemFlags>(f);
        }
        }
        return Qt::NoItemFlags;
    }

    virtual QVariant data(int col, int role) const
    {
        if(role == Qt::DisplayRole)
            switch(col)
            {
            case PROPERTY_NAME: return QVariant(m_Name);
            case PROPERTY_VALUE: return QVariant(convert_value(getChildrenCount()).c_str());
            }
        return QVariant();
    }

    virtual bool setData(const QVariant& value, int role = Qt::EditRole)
    {
        if(!value.canConvert<unsigned>())
            return false;
        unsigned count = value.toUInt();
        if(count > getChildrenCount())
            for(size_t i = 0; i < count; ++i)
            {
                addChild(make_aligned_shared<child_type>(convert_value(i).c_str()));
                connect(&getChild(i), SIGNAL(changed()), this, SLOT(update()));
            }
        else
            removeChildren(getChildrenCount() - count, getChildrenCount());
        emit changed();
        return true;
    }

protected:
    virtual void _changed() {}
};

template<class T>
struct CESTypeLookup;

#define CES_TYPE_LOOKUP(ces_t, carina_t) \
    template<> \
    struct CESTypeLookup<carina_t> { \
        typedef ces_t ces_type; \
    }
CES_TYPE_LOOKUP(float, float);
CES_TYPE_LOOKUP(vec2_t, Vector2);
CES_TYPE_LOOKUP(vec3_t, Vector3);
CES_TYPE_LOOKUP(vec4_t, Vector4);
CES_TYPE_LOOKUP(mat4_t, Matrix4);
CES_TYPE_LOOKUP(int32, int32);
CES_TYPE_LOOKUP(uint32, uint32);

template<class T>
class EditableProperty: public ValueProperty<T>
{
	typedef shared_ptr<TypedAttribute<T>> SimpleAttributePtr;
    CESAttribute&       m_CESAttribute;
    SimpleAttributePtr  m_Attribute;
public:
    EditableProperty(CESAttribute& ces_attr, const SimpleAttributePtr& attr)
        :   ValueProperty<T>(attr->getName().c_str()),
            m_CESAttribute(ces_attr),
            m_Attribute(attr) 
    {
        T t;
        m_Attribute->getValue(&t);
        this->setValue(t);
    }

    virtual VarRange getRange() const { return m_Attribute->getRange(); }

protected:
    virtual void _changed()
    {
        ValueProperty<T>::_changed();
        T t = this->getValue();
        m_CESAttribute.set(*reinterpret_cast<const typename CESTypeLookup<T>::ces_type*>(&t));
        m_Attribute->setValue(&t);
    }
};

template<template<class U, class V> class TArray, class T, class TAlloc>
class EditableProperty< TArray<T, TAlloc> >: public ValueProperty< DynamicArray<T> >
{
	typedef shared_ptr<TypedAttribute<T>> ContainerAttributePtr;
    CESAttribute&           m_CESAttribute;
    ContainerAttributePtr   m_Attribute;
public:
    EditableProperty(CESAttribute& ces_attr, const ContainerAttributePtr& attr)
        :   ValueProperty< DynamicArray<T> >(attr->getName().c_str()),
            m_CESAttribute(ces_attr),
            m_Attribute(attr) 
    {
        DynamicArray<T> arr(m_Attribute->size());
        m_Attribute->getValue(arr.get());
        this->setValue(arr);
    }

protected:
    virtual void _changed()
    {
        ValueProperty< DynamicArray<T> >::_changed();
        DynamicArray<T> t(this->getValue());
        m_CESAttribute.set(*reinterpret_cast<const DynamicArray<typename CESTypeLookup<T>::ces_type>*>(&t));
        m_Attribute->setValue(t.get(), t.size());
    }
};

#endif /* _CARINA_EDITOR_PROPERTY_HH_ */
